#include <list>
#include <simulationOutils/job.h>
#include <simulationOutils/parser.h>

#include <stdio.h>
#include "msg/msg.h"            /* Yeah! If you want to use msg, you need to include msg/msg.h */
#include "xbt/sysdep.h"         /* calloc, printf */

/* Create a log channel to have nice outputs. */
#include "xbt/log.h"
#include "xbt/asserts.h"

#include <pthread.h>

#include <map>



#define NORMAL_TASK 0x00;
#define ENDING_SIGNAL_TASK 0xFACE0102

int normalTask = NORMAL_TASK;
int endingSignalTask = ENDING_SIGNAL_TASK;

typedef enum {
  PORT_22 = 0,
  MAX_CHANNEL = 1
} channel_t;

class ProcTableSimulation {
	private:
	
	pthread_mutex_t mutex;
	//std::map<m_host_t, bool> availabiltyMap;
	
	m_host_t* hosts_table;
	
	public:
	m_host_t masterHost;
	int nbHosts;
	int nbSlaves;
	
	ProcTableSimulation () {
		pthread_mutex_init(&mutex,NULL);
		nbHosts = MSG_get_host_number();
		hosts_table = MSG_get_host_table();
		nbSlaves = nbHosts - 1;
	}
	
	void setMaster(char* nameOfTheMaster) {
		for(int i = 0; i < nbHosts; i++) {
			if (strcmp(hosts_table[i]->name, nameOfTheMaster) == 0) {
				masterHost = hosts_table[i];
				return;
			}
		}
	}
	
	~ProcTableSimulation () {
		pthread_mutex_destroy(&mutex);
	}
	
	
	std::list<m_host_t>* getAvailableSlaves() {
		std::list<m_host_t>* res = new std::list<m_host_t> ;
		
		for(int i = 0; i < nbHosts; i++) {
			m_host_t host = hosts_table[i];
			if (host != masterHost && MSG_host_is_avail(host)) {
				res->push_back(hosts_table[i]);
			}
		}
		
		return res;
	}
};


class  JobQueueSimulation {
	private:
	pthread_mutex_t mutex;
	
	std::list<Job> workload;
	std::list<Job> waitingQueue;
	
	
	
	public:
	
	JobQueueSimulation(char* wldPath) {
		pthread_mutex_init(&mutex,NULL);

		
		parse(wldPath, "task", workload);
	}
	
	~JobQueueSimulation() {
		pthread_mutex_destroy(&mutex);
	}
	
	
	Job& getFirstJob() {
		
		pthread_mutex_lock(&mutex);
		
		Job& job = waitingQueue.front();
		
		pthread_mutex_unlock(&mutex);
		
		return job;
	}
	
	void popFirstJob() {
		pthread_mutex_lock(&mutex);
		
		waitingQueue.pop_front();
		
		pthread_mutex_unlock(&mutex);
	}
	
	void jobEntersTheWaitingQueue (double current_time = -1) {
		pthread_mutex_lock(&mutex);
		
		std::list<Job>::iterator iter;
		
		
		//add the jobs in the waiting queue	
		
		int n = 0;
		bool end = false;
		for (iter = workload.begin(); iter != workload.end() && !end; iter++) {
			Job& job = *iter;
			if (job.submit_time <= current_time) {
				waitingQueue.push_back(job);
				
				//std::cout << job << "\n";
				
				n++;
			}
			else if (current_time < 0.0) {
				n = 1;
				waitingQueue.push_back(job);
				end = true;
			}
			else
			{
				end = true;
			}
		}
		
		//removes the n first job from the workload
		for (int i = 0; i < n; i++) {
			workload.pop_front();
		}
		
		
		
		pthread_mutex_unlock(&mutex);
	}
	
	bool workload_reaches_empty() {
		
		pthread_mutex_lock(&mutex);
		bool empty = workload.empty();
		pthread_mutex_unlock(&mutex);
		
		return empty;
	}
	
	void distributeJob() {
		
	}	
	
	private:
	
};


class Cluster {
public:
	ProcTableSimulation* procTableSimulation;
	JobQueueSimulation* jobQueueSimulation;
	
	
	Cluster(char* wldPath) {
		jobQueueSimulation = new JobQueueSimulation(wldPath);
		procTableSimulation = new ProcTableSimulation;
	}
	
	Cluster(ProcTableSimulation* p, JobQueueSimulation* j) {
		procTableSimulation = p;
		jobQueueSimulation = j;
	}
	
	~Cluster() {
		delete jobQueueSimulation;
		delete procTableSimulation;
	}

};


/*
void simulate() {
	int nbArgs = 1;
	char* argv[] = { NULL };
	SD_init(&nbArgs, argv);

	SD_create_environment(platformFile);
}
*/


/* Copyright (c) 2007, 2008, 2009, 2010. The SimGrid Team.
 * All rights reserved.                                                     */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

XBT_LOG_NEW_DEFAULT_CATEGORY(msg_test,
                             "Messages specific for this msg example");

int test(int argc, char *argv[]);
MSG_error_t test_all(const char *platform_file);


int waitingQueueManager(int argc, char* argv[]) {
	
	Cluster* cluster = (Cluster*) MSG_process_get_data(MSG_process_self());
	JobQueueSimulation* jobQueueSimulation = cluster->jobQueueSimulation;
	ProcTableSimulation* procTableSimulation = cluster->procTableSimulation;
	
	
	//while (! jobQueueSimulation->workload_reaches_empty()) {
		double current_time;
		
		
		jobQueueSimulation->jobEntersTheWaitingQueue(-1.0);
		Job& job = jobQueueSimulation->getFirstJob();
		
		std::cout << job << "\n";
		
		printf("submit_time = %lf\n", job.submit_time);
		MSG_process_sleep(job.submit_time);
		
		m_task_t task = MSG_task_create(job.name, job.run_time, 0, &normalTask);
		
		
		std::list<m_host_t>* availableHosts = procTableSimulation->getAvailableSlaves();
		
		printf("available hosts # = %d\n", availableHosts->size());
		
		std::list<m_host_t>::iterator iter;
		
		//algo fifo
		
		int n = 1;
		m_host_t hostDst;
		for (iter = availableHosts->begin(); iter != availableHosts->end() && n <= job.nb_procs ; iter++) {
			hostDst = *iter;
			MSG_task_put (task, hostDst, MAX_CHANNEL);
			n++;
		}
		
		
		delete availableHosts;
		
		current_time = MSG_get_clock();
		printf ("Date : %lf\n", current_time);
		
	//	jobQueueSimulation->jobEntersTheWaitingQueue(current_time);
	//}
	
	
	return 0;
}

int taskExecutor(int argc, char* argv[]) {
	
	//while(true) {
	//	sleep(1);
		printf("%s :%lf\n", MSG_host_self()->name, MSG_get_clock());
		
		m_task_t task;
		
		int a = MSG_task_get_with_timeout(&task, MAX_CHANNEL, 1e+200);
		if (a == MSG_OK) {
			MSG_task_execute(task);
		}
		
	//}
	
	return 0;
}


/** Emitter function  */
int test(int argc, char *argv[])
{
  int slaves_count = 0;
  m_host_t *slaves = NULL;
  double task_comp_size = 100000;
  double task_comm_size = 0;
  double *computation_amount = NULL;
  double *communication_amount = NULL;
  m_task_t ptask = NULL;
  int i, j;
	
	
  std::list<Job> jobsList;
	
  printf("workload : %s \n", argv[0]);
  parse(argv[0], "task", jobsList);
  
  
  slaves_count = MSG_get_host_number();
  slaves = MSG_get_host_table();

  computation_amount = xbt_new0(double, slaves_count);
  communication_amount = xbt_new0(double, slaves_count * slaves_count);

  for (i = 0; i < slaves_count; i++)
    computation_amount[i] = task_comp_size;

  for (i = 0; i < slaves_count; i++)
    for (j = i + 1; j < slaves_count; j++)
      communication_amount[i * slaves_count + j] = task_comm_size;


  //we iterate on the items of the list


  ptask = MSG_parallel_task_create("parallel task",
                                   slaves_count, slaves,
                                   computation_amount,
                                   communication_amount, NULL);
  MSG_parallel_task_execute(ptask);

  /* There is no need to free that! */
/*   free(communication_amount); */
/*   free(computation_amount); */

  INFO0("Goodbye now!");
  free(slaves);
  return 0;
}

/** Test function */
MSG_error_t test_all(const char *platform_file, char* wldFile)
{
  MSG_error_t res = MSG_OK;

  MSG_config("workstation/model", "ptask_L07");
  MSG_set_channel_number(MAX_CHANNEL);
  MSG_create_environment(platform_file);

  Cluster* cluster = new Cluster(wldFile);

  
  cluster->procTableSimulation->setMaster("Master");
	
  char name[512];
  for(int i = 1; i < cluster->procTableSimulation->nbHosts; i++) {
  		
  		if (MSG_get_host_table()[i] != cluster->procTableSimulation->masterHost) {
	  		sprintf(name, "ExecutorProcessOn%s", MSG_get_host_table()[i]->name);
	  		printf("%s\n", name);
	  		
	  		//MSG_function_register(name, taskExecutor);
	  		
	  		MSG_process_create(name, taskExecutor, (void*) cluster, MSG_get_host_table()[i]);
  		}
  }

	MSG_process_create("waitingQueueManagerProcess", waitingQueueManager, (void*) cluster, cluster->procTableSimulation->masterHost);


  //MSG_process_create("test", test, NULL, MSG_get_host_table()[0]);
  res = MSG_main();

  INFO1("Simulation time %g", MSG_get_clock());
  
  delete cluster;
  
  return res;
}

int main(int argc, char *argv[])
{
  MSG_error_t res = MSG_OK;

  MSG_global_init(&argc, argv);
  if (argc < 3) {
    printf("Usage: %s platform_file wld_file\n", argv[0]);
    exit(1);
  }
  res = test_all(argv[1], argv[2]);
  MSG_clean();

  if (res == MSG_OK)
    return 0;
  else
    return 1;
}


/*
int main(int argc, char** argv) {
	
	std::list<Job> jobsList;
	
	parse(argv[1], "task", jobsList);
	
	simulate();	
	
	
	return 0;
}

*/
